document.write(`
`);
class Chat {
constructor(room, username = '', avatar) {
this.server = 'https://www.shoutbox.com';
this.AJAX = `${this.server}/chat/ajax.php`;
this.myUser = {
username,
room,
avatar: avatar || `${this.server}/avatars/${Math.ceil(Math.random() * 29)}.svg`,
password: '',
id: Date.now(),
isAdmin: false
};
this.users = {};
window.shoutbox = this;
this.traductions = {
welcome: "Welcome %s. ",
userOnline: "%s user online",
usersOnline: "%s users online",
enterYourTextHere: "Enter your text here",
serverMessage: "%s
",
enterAdminPassword: "Enter admin password",
imageAvatar: "",
youAreAdminNow: "You are admin now.",
mp3: "https://www.shoutbox.com/chat/mp3/dink.mp3",
addUser: "",
banText: "",
receivedText: "%s%s %s: %s
",
youBannedUser: "You banned %s"
};
this.smileys = {
':)': '๐', ';)': '๐', ':D': '๐', 'xD': '๐', ':(': '๐', ":'(": '๐ข',
'>:(': '๐ ', ':O': '๐ฎ', ':$': '๐ณ', ':|': '๐', '<3': 'โค'
};
// quick sprintf
this.sprintf = (str, ...argv) => !argv.length ? str : this.sprintf(str.replace(/%s/, argv.shift()), ...argv);
// polyfill replaceAll if needed
if (!String.prototype.replaceAll) {
// eslint-disable-next-line no-extend-native
String.prototype.replaceAll = function (target, replacement) { return this.split(target).join(replacement); };
}
// Prefill admin fields
try {
const email = localStorage.getItem('email');
const pwd = localStorage.getItem('password');
if (email) jQuery('#shoutboxEmailAdmin').val(email);
if (pwd) jQuery('#shoutboxPasswordAdmin').val(pwd);
} catch (_) {}
this.bindUI();
this.initSocket();
this.bootstrap();
}
/* ---------- UI helpers ---------- */
parseSmileys = (text) => {
Object.keys(this.smileys).forEach(s => { text = text.replaceAll(s, this.smileys[s]); });
return text;
};
stripHTML = (html) => {
const div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
};
clearChat = () => { const el = document.querySelector('#shoutChat'); if (el) el.innerHTML = ''; };
serverMessage = (text) => {
const $shoutChat = jQuery('#shoutChat');
$shoutChat.append(this.sprintf(this.traductions.serverMessage, text));
$shoutChat.animate({ scrollTop: $shoutChat[0].scrollHeight }, 300);
};
showAd = () => {
const html = `Get your free shoutbox with no ads for 9.90โฌ/year`;
this.serverMessage(html);
};
getColor = (username) => {
const colors = ['#FFB900','#D83B01','#B50E0E','#E81123','#B4009E','#5C2D91','#0078D7','#00B4FF','#008272','#107C10'];
let sum = 0;
for (let i = 0; i < username.length; i++) sum += username.charCodeAt(i);
return colors[sum % colors.length];
};
getAvatar = (image, username = '') => {
const color = this.getColor(username);
if (image.indexOf(this.server) === 0) {
const firstLetter = (username.charAt(0) || '').toUpperCase();
const secondLetter = (username.charAt(1) || '').toUpperCase();
return ``;
}
return this.sprintf(this.traductions.imageAvatar, image);
};
receiveText = (username, message, date, scrollTimer, avatar, ip, id) => {
username = this.stripHTML(username || '');
message = this.parseSmileys(message || '');
if (avatar) avatar = this.getAvatar(avatar, username);
if (date) date = this.sprintf('(%s)', date);
const html = this.sprintf(this.traductions.receivedText, id, ip, avatar || '', date || '', username, message);
const $shoutChat = jQuery('#shoutChat');
$shoutChat.animate({ scrollTop: $shoutChat[0].scrollHeight }, scrollTimer || 0);
jQuery(html).hide().appendTo('#shoutChat').fadeIn(200);
if (this.myUser.isAdmin) jQuery(`div[data-id="${id}"]`).addClass('shoutboxAdmin');
};
addUser = (user) => {
if (!user.username) user.username = this.getRandomUsername();
this.updateNumberUsersDisplay();
let avatar = user.avatar;
if (avatar) avatar = this.getAvatar(avatar, user.username);
const txt = this.sprintf(this.traductions.addUser, user.id, user.id, avatar || '', user.username);
jQuery('#shoutBoxUserList').append(txt);
};
updateNumberUsersDisplay = () => {
const len = Object.keys(this.users || {}).length;
const text = (len > 1)
? this.sprintf(this.traductions.usersOnline, len)
: this.sprintf(this.traductions.userOnline, len);
jQuery('#shoutBoxHeaderText').text(text);
};
getRandomUsername = () => {
const a = ['Small','Blue','Ugly','Big','Red','Yellow','Green','Nice','Cool'];
const b = ['Bear','Dog','Banana','John','Joe','Jack','Chatter','Fish','Bird'];
return `${a[Math.floor(Math.random()*a.length)]}${b[Math.floor(Math.random()*b.length)]}`;
};
welcome = () => {
const $in = jQuery('#shoutBoxInput');
$in.attr('placeholder', this.traductions.enterYourTextHere);
this.serverMessage(this.sprintf(this.traductions.welcome, this.myUser.username));
$in.removeClass('shoutInputRed');
try { localStorage.setItem('username', this.myUser.username); } catch (_) {}
};
/* ---------- Data flows ---------- */
refreshChat = async () => {
this.clearChat();
const fd = new FormData();
fd.append('a', 'getLastMessages');
fd.append('id', this.myUser.room);
const res = await fetch(this.AJAX, { method: 'POST', body: fd });
const messages = await res.json().catch(() => []);
for (let i = messages.length - 1; i >= 0; i--) {
const m = messages[i];
this.receiveText(m.username, m.message, m.date, 0, m.avatar, m.ip, m.id);
}
};
getLastMessages = async () => {
const fd = new FormData();
fd.append('a', 'getLastMessages');
fd.append('id', this.myUser.room);
const res = await fetch(this.AJAX, { method: 'POST', body: fd });
const messages = await res.json().catch(() => []);
for (let i = messages.length - 1; i >= 0; i--) {
const m = messages[i];
this.receiveText(m.username, m.message, m.date, 0, m.avatar, m.ip, m.id);
}
if (this.myUser.username) this.welcome();
};
/* ---------- Socket.IO ---------- */
initSocket = () => {
this.shoutboxSocket = io.connect(`${this.server}:8443`);
this.shoutboxSocket.on('connect', () => {
const stored = this.stripHTML(localStorage.getItem('username') || '');
if (stored) this.myUser.username = stored;
this.shoutboxSocket.emit('enterRoom', this.myUser);
});
this.shoutboxSocket.on('roomEntered', () => {
const stored = this.stripHTML(localStorage.getItem('username') || '');
if (stored) this.welcome();
});
this.shoutboxSocket.on('del', (id) => jQuery(`[data-id="${id}"]`).remove());
// FIX: correct selector (quote IP attr)
this.shoutboxSocket.on('ban', (ip) => jQuery(`[data-ip="${ip}"]`).remove());
this.shoutboxSocket.on('receiveText', (user, message, ip, id) => {
this.receiveText(user.username, message, '', 200, user.avatar, ip, id);
try { new Audio(this.traductions.mp3).play(); } catch (_) {}
});
this.shoutboxSocket.on('userChanged', (user) => {
let avatar = user.avatar ? this.getAvatar(user.avatar, user.username) : '';
const txt = this.sprintf(this.traductions.addUser, user.id, user.id, avatar, user.username);
jQuery(`#shoutBoxUser${user.id}`).html(txt);
});
this.shoutboxSocket.on('setAdminMode', (password) => {
this.setAdminMode(password);
jQuery('div.shoutText').addClass('shoutboxAdmin');
});
this.shoutboxSocket.on('addUser', (user) => {
this.users[user.id] = user;
this.addUser(user);
});
this.shoutboxSocket.on('removeUser', (user) => {
delete this.users[user.id];
this.updateNumberUsersDisplay();
jQuery(`#shoutBoxUser${user.id}`).remove();
});
this.shoutboxSocket.on('error', (err) => console.log(err));
};
/* ---------- Admin & actions ---------- */
setAdminMode = (password) => {
this.myUser.password = password;
this.myUser.isAdmin = true;
this.serverMessage(this.traductions.youAreAdminNow);
jQuery('#shoutboxAdminLoginBtn').toggle();
};
sendText = () => {
const $in = jQuery('#shoutBoxInput');
let text = ($in.val() || '').trim();
text = this.stripHTML(text);
if (!text) return;
if (!this.myUser.username) {
this.myUser.username = text;
$in.val('');
this.welcome();
this.shoutboxSocket.emit('changeUser', this.myUser);
return;
}
$in.val('');
this.shoutboxSocket.emit('send', this.myUser, text);
$in.prop('disabled', true);
setTimeout(() => { $in.prop('disabled', false); $in.focus(); }, 800);
};
/* ============ Emoji Picker ============ */
initEmojiPicker = () => {
const RECENT_KEY = 'shout_recent_emojis';
const MAX_RECENT = 24;
const EMOJI_LIST = [
"๐","๐","๐","๐คฃ","๐","๐","๐
","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐ค","๐คญ","๐คซ","๐ค","๐คจ","๐",
"๐","๐ถ","๐","๐","๐ฃ","๐ฅ","๐ฎ","๐ค","๐ฏ","๐ช","๐ซ","๐ฅฑ","๐ด","๐","๐","๐","๐คช","๐","๐คค","๐","๐","๐","๐",
"๐","โน๏ธ","๐","๐","๐","๐ค","๐ข","๐ญ","๐ฆ","๐ง","๐จ","๐ฉ","๐คฏ","๐ฌ","๐ฎโ๐จ","๐ฑ","๐ฅต","๐ฅถ","๐ณ","๐ซ ","๐","๐ค ",
"๐บ","๐ธ","๐น","๐ป","๐ผ","๐","๐","๐","๐","๐ค","๐","๐ช","๐ฏ","๐","โจ","๐ฅ","๐ฅ","๐","๐","๐","๐ฅณ","โฝ","๐","๐ฎ",
"๐","๐","๐","๐ฎ","๐ฃ","๐ฐ","๐บ","๐ท","โ","๐ต","๐","๐","โญ","โ","๐","๐","โ๏ธ","๐ ","๐ฑ","๐ก",
"โค๏ธ","๐งก","๐","๐","๐","๐","๐ค","๐ค","๐ค"
];
const $in = jQuery('#shoutBoxInput');
if (!$in.length) return;
// 1) Wrap the input if needed so we can anchor UI
if (!$in.parent().hasClass('sb-inputWrap')) $in.wrap('');
const $wrap = $in.parent();
// 2) Inject button + panel if missing
if (!jQuery('#sbEmojiBtn').length) {
$wrap.append('');
}
if (!jQuery('#sbEmojiPanel').length) {
$wrap.append(
''
);
}
const $btn = jQuery('#sbEmojiBtn');
const $panel = jQuery('#sbEmojiPanel');
const $recentS = jQuery('#sbEmojiRecent');
const $allS = jQuery('#sbEmojiAll');
const $search = jQuery('#sbEmojiSearch');
const $close = jQuery('#sbEmojiClose');
// Ensure input padding (so text doesn't sit under the button)
const pr = parseInt(window.getComputedStyle($in.get(0)).paddingRight || '12', 10);
if (pr < 54) $in.css('padding-right','54px');
// --- helpers ---
const loadRecent = () => { try { return JSON.parse(localStorage.getItem(RECENT_KEY) || '[]'); } catch { return []; } };
const saveRecent = (arr) => localStorage.setItem(RECENT_KEY, JSON.stringify(arr.slice(0, MAX_RECENT)));
const bumpRecent = (emoji) => { const r = loadRecent().filter(e => e !== emoji); r.unshift(emoji); saveRecent(r); };
const insertAtCursor = (inputEl, text) => {
const start = inputEl.selectionStart ?? inputEl.value.length;
const end = inputEl.selectionEnd ?? inputEl.value.length;
const val = inputEl.value;
inputEl.value = val.slice(0,start) + text + val.slice(end);
const pos = start + text.length;
inputEl.setSelectionRange(pos, pos);
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
inputEl.focus();
};
const buildGrid = (list, $container) => {
const $grid = jQuery('');
list.forEach(e => {
jQuery('')
.attr('aria-label', e)
.text(e)
.on('click', () => { insertAtCursor($in.get(0), e); bumpRecent(e); renderRecent(); })
.appendTo($grid);
});
$container.empty().append($grid);
};
const renderRecent = () => {
const r = loadRecent();
if (r.length) { buildGrid(r, $recentS); $recentS.removeAttr('hidden'); }
else { $recentS.attr('hidden',''); $recentS.empty(); }
};
const renderAll = (filter='') => {
const f = (filter || '').toLowerCase();
const base = f ? EMOJI_LIST.filter(e => e.toLowerCase().includes(f)) : EMOJI_LIST;
buildGrid(base, $allS);
};
const openPicker = () => {
renderRecent(); renderAll();
$panel.removeAttr('hidden'); $btn.attr('aria-expanded','true');
if (window.innerWidth > 600) { $search.val('').focus(); }
document.addEventListener('click', outsideClose, true);
document.addEventListener('keydown', escClose, true);
};
const closePicker = () => {
$panel.attr('hidden',''); $btn.attr('aria-expanded','false');
document.removeEventListener('click', outsideClose, true);
document.removeEventListener('keydown', escClose, true);
};
const outsideClose = (ev) => {
if ($panel.get(0).contains(ev.target) || $btn.get(0).contains(ev.target)) return;
closePicker();
};
const escClose = (ev) => { if (ev.key === 'Escape') closePicker(); };
// --- events ---
$btn.on('click', (e) => { e.stopPropagation(); $panel.is(':hidden') ? openPicker() : closePicker(); });
$close.on('click', closePicker);
$search.on('input', () => renderAll($search.val()));
// Optional: Ctrl/Cmd + E shortcut
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'e') {
e.preventDefault(); $panel.is(':hidden') ? openPicker() : closePicker();
}
});
};
/* ============ Emoji Picker ============ */
initEmojiPicker = () => {
const RECENT_KEY = 'shout_recent_emojis';
const MAX_RECENT = 24;
const EMOJI_LIST = [
"๐","๐","๐","๐คฃ","๐","๐","๐
","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐","๐ค","๐คญ","๐คซ","๐ค","๐คจ","๐",
"๐","๐ถ","๐","๐","๐ฃ","๐ฅ","๐ฎ","๐ค","๐ฏ","๐ช","๐ซ","๐ฅฑ","๐ด","๐","๐","๐","๐คช","๐","๐คค","๐","๐","๐","๐",
"๐","โน๏ธ","๐","๐","๐","๐ค","๐ข","๐ญ","๐ฆ","๐ง","๐จ","๐ฉ","๐คฏ","๐ฌ","๐ฎโ๐จ","๐ฑ","๐ฅต","๐ฅถ","๐ณ","๐ซ ","๐","๐ค ",
"๐บ","๐ธ","๐น","๐ป","๐ผ","๐","๐","๐","๐","๐ค","๐","๐ช","๐ฏ","๐","โจ","๐ฅ","๐ฅ","๐","๐","๐","๐ฅณ","โฝ","๐","๐ฎ",
"๐","๐","๐","๐ฎ","๐ฃ","๐ฐ","๐บ","๐ท","โ","๐ต","๐","๐","โญ","โ","๐","๐","โ๏ธ","๐ ","๐ฑ","๐ก",
"โค๏ธ","๐งก","๐","๐","๐","๐","๐ค","๐ค","๐ค"
];
const $in = jQuery('#shoutBoxInput');
if (!$in.length) return;
// 1) Wrap the input if needed so we can anchor UI
if (!$in.parent().hasClass('sb-inputWrap')) $in.wrap('');
const $wrap = $in.parent();
// 2) Inject button + panel if missing
if (!jQuery('#sbEmojiBtn').length) {
$wrap.append('');
}
if (!jQuery('#sbEmojiPanel').length) {
$wrap.append(
''
);
}
const $btn = jQuery('#sbEmojiBtn');
const $panel = jQuery('#sbEmojiPanel');
const $recentS = jQuery('#sbEmojiRecent');
const $allS = jQuery('#sbEmojiAll');
const $search = jQuery('#sbEmojiSearch');
const $close = jQuery('#sbEmojiClose');
// Ensure input padding (so text doesn't sit under the button)
const pr = parseInt(window.getComputedStyle($in.get(0)).paddingRight || '12', 10);
if (pr < 54) $in.css('padding-right','54px');
// --- helpers ---
const loadRecent = () => { try { return JSON.parse(localStorage.getItem(RECENT_KEY) || '[]'); } catch { return []; } };
const saveRecent = (arr) => localStorage.setItem(RECENT_KEY, JSON.stringify(arr.slice(0, MAX_RECENT)));
const bumpRecent = (emoji) => { const r = loadRecent().filter(e => e !== emoji); r.unshift(emoji); saveRecent(r); };
const insertAtCursor = (inputEl, text) => {
const start = inputEl.selectionStart ?? inputEl.value.length;
const end = inputEl.selectionEnd ?? inputEl.value.length;
const val = inputEl.value;
inputEl.value = val.slice(0,start) + text + val.slice(end);
const pos = start + text.length;
inputEl.setSelectionRange(pos, pos);
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
inputEl.focus();
};
const buildGrid = (list, $container) => {
const $grid = jQuery('');
list.forEach(e => {
jQuery('')
.attr('aria-label', e)
.text(e)
.on('click', () => { insertAtCursor($in.get(0), e); bumpRecent(e); renderRecent(); })
.appendTo($grid);
});
$container.empty().append($grid);
};
const renderRecent = () => {
const r = loadRecent();
if (r.length) { buildGrid(r, $recentS); $recentS.removeAttr('hidden'); }
else { $recentS.attr('hidden',''); $recentS.empty(); }
};
const renderAll = (filter='') => {
const f = (filter || '').toLowerCase();
const base = f ? EMOJI_LIST.filter(e => e.toLowerCase().includes(f)) : EMOJI_LIST;
buildGrid(base, $allS);
};
const openPicker = () => {
renderRecent(); renderAll();
$panel.removeAttr('hidden'); $btn.attr('aria-expanded','true');
if (window.innerWidth > 600) { $search.val('').focus(); }
document.addEventListener('click', outsideClose, true);
document.addEventListener('keydown', escClose, true);
};
const closePicker = () => {
$panel.attr('hidden',''); $btn.attr('aria-expanded','false');
document.removeEventListener('click', outsideClose, true);
document.removeEventListener('keydown', escClose, true);
};
const outsideClose = (ev) => {
if ($panel.get(0).contains(ev.target) || $btn.get(0).contains(ev.target)) return;
closePicker();
};
const escClose = (ev) => { if (ev.key === 'Escape') closePicker(); };
// --- events ---
$btn.on('click', (e) => { e.stopPropagation(); $panel.is(':hidden') ? openPicker() : closePicker(); });
$close.on('click', closePicker);
$search.on('input', () => renderAll($search.val()));
// Optional: Ctrl/Cmd + E shortcut
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'e') {
e.preventDefault(); $panel.is(':hidden') ? openPicker() : closePicker();
}
});
};
/* ---------- Bind UI ---------- */
bindUI = () => {
this.initEmojiPicker();
const $container = jQuery('.shoutBoxContainer');
const $loginPanel = jQuery('#shoutboxLoginPanel');
const $pwdChange = jQuery('#shoutboxAdminPasswordChangePanel');
const $forgotten = jQuery('#shoutboxForgottenPassword');
jQuery('#shoutBoxInput').on('keypress', (e) => { if ((e.keyCode || e.which) === 13) this.sendText(); });
$container.on('click', '.shoutboxBanBtn', (e) => {
const $el = jQuery(e.currentTarget);
const ip = $el.closest('div').data('ip');
this.shoutboxSocket.emit('ban', ip);
const uname = $el.parent().find('.shoutUserText').text();
this.serverMessage(this.sprintf(this.traductions.youBannedUser, uname));
});
$container.on('click', '.shoutboxDelBtn', (e) => {
const id = jQuery(e.currentTarget).closest('div').data('id');
this.shoutboxSocket.emit('del', id);
});
jQuery(document).on('click', '.shoutboxChangeUsernameBtn', () => {
localStorage.clear();
this.myUser.username = '';
const $in = jQuery('#shoutBoxInput');
$in.addClass('shoutInputRed').val('').attr('placeholder', 'Enter new username').focus();
});
jQuery('#shoutboxForgottenBtn').click(() => $forgotten.slideToggle(200));
jQuery('#shoutboxAdminLoginBtn').click((e) => {
e.stopImmediatePropagation();
if (this.myUser.isAdmin) {
$loginPanel.hide();
$pwdChange.slideToggle();
$forgotten.hide();
} else {
$loginPanel.slideToggle(200);
$pwdChange.hide();
$forgotten.hide();
}
});
jQuery('#shoutBoxHeader').click(() => jQuery('#shoutBoxUserList').toggle('fast'));
jQuery('#shoutboxSaveConfigBtn').click(async (e) => {
const $_err = jQuery(e.currentTarget).closest('.panel, .configPanel').find('.error');
const mustRegister = jQuery('#shoutboxUserMustRegister').is(':checked');
const oldPwd = jQuery('#shoutboxChangeOldPassword').val();
const newPwd = jQuery('#shoutboxChangeNewPassword').val();
if ((oldPwd || '').length < 3 || (newPwd || '').length < 3) {
this.displayError($_err, 'Invalid Password'); return;
}
jQuery('.error').empty();
const fd = new FormData();
fd.append('a', 'updateAdmin');
fd.append('shoutboxUserMustRegister', mustRegister);
fd.append('oldPassword', oldPwd);
fd.append('newPassword', newPwd);
const res = await fetch(this.AJAX, { method: 'POST', body: fd });
let txt = await res.text();
if (txt === 'ko') { this.displayError($_err, 'Invalid email/password'); return; }
let user;
try { user = JSON.parse(txt); } catch { user = null; }
if (!user) { this.displayError($_err, 'Server error'); return; }
this.myUser.password = user.password;
this.myUser.shoutboxUserMustRegister = user.shoutboxUserMustRegister;
$pwdChange.slideToggle(200);
$loginPanel.hide();
});
jQuery('#shoutboxLoginAdminBtn').click(async (e) => {
const $_err = jQuery(e.currentTarget).closest('.panel, .loginPanel').find('.error');
const email = jQuery('#shoutboxEmailAdmin').val();
const password = jQuery('#shoutboxPasswordAdmin').val();
if ((password || '').length < 3) { this.displayError($_err, 'Invalid Password'); return; }
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (!re.test(email || '')) { this.displayError($_err, 'Invalid email'); return; }
jQuery('.error').empty();
const fd = new FormData();
fd.append('a', 'loginAdmin'); fd.append('email', email); fd.append('password', password);
const res = await fetch(this.AJAX, { method: 'POST', body: fd });
let txt = await res.text();
if (txt === 'ko') { this.displayError($_err, 'Invalid email/password'); return; }
let user;
try { user = JSON.parse(txt); } catch { user = null; }
if (!user) { this.displayError($_err, 'Server error'); return; }
this.myUser.username = 'admin';
this.myUser.password = user.password;
this.myUser.shoutboxUserMustRegister = user.shoutboxUserMustRegister;
this.myUser.isAdmin = true;
this.myUser.avatar = `${this.server}/avatars/admin.svg`;
$loginPanel.hide();
this.serverMessage(this.traductions.youAreAdminNow);
try { localStorage.setItem('email', email); localStorage.setItem('password', password); } catch {}
this.shoutboxSocket.emit('checkPassword', password);
});
jQuery('#sendMyPasswordBtn').click(async () => {
const $_err = jQuery('#shoutboxForgottenPassword').find('.error');
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const email = jQuery('#shoutboxForgottenEmail').val();
if (!re.test(email || '')) { this.displayError($_err, 'Invalid email'); return; }
const fd = new FormData();
fd.append('a', 'forgottenshoutboxPasswordAdmin');
fd.append('email', email);
const res = await fetch(this.AJAX, { method: 'POST', body: fd });
const txt = await res.text();
if (txt === 'ko') { this.displayError($_err, 'No such email !'); return; }
jQuery('#shoutboxForgottenPassword').hide(200);
});
jQuery('.shoutBoxContainer').on('click', '.shoutBoxUserItem', (e) => {
e.stopImmediatePropagation();
const userId = jQuery(e.currentTarget).data('id');
this.openPrivateChat(userId);
});
};
displayError = ($el, message) => { $el.html(message); setTimeout(() => $el.empty(), 3000); };
openPrivateChat = (userid) => { /* TODO: DM */ };
/* ---------- First data bootstrap ---------- */
bootstrap = async () => {
await this.getLastMessages();
// Webmaster check
const fd = new FormData();
fd.append('a', 'getWebmaster');
fd.append('id', this.myUser.room);
const res = await fetch(this.AJAX, { method: 'POST', body: fd });
const data = await res.json().catch(() => ({}));
if (data['squat'] === 'squat') { window.location = this.server; return; }
if (!data['paid']) {
if (parseInt(data.entries || '0', 10) > 50) this.showAd();
setInterval(() => this.showAd(), 90000);
}
};
}
/* ---- Auto-start if script filename ends with numeric webmaster id ---- */
(() => {
const src = document.currentScript && document.currentScript.src;
let webmasterid = 0;
if (src) {
const tail = src.split('/').pop();
webmasterid = Number(tail);
}
if (Number.isInteger ? Number.isInteger(webmasterid) : (typeof webmasterid === 'number' && isFinite(webmasterid) && Math.floor(webmasterid) === webmasterid)) {
if (webmasterid > 0) setTimeout(() => { new Chat(webmasterid); }, 1000);
}
})();